package co.baiku.boot.core.orm.specification;

import co.baiku.boot.common.spring.SpringContext;
import co.baiku.boot.common.tools.JsonTools;
import co.baiku.boot.common.tools.ObjectTools;
import co.baiku.boot.config.AjavaerConfig;
import co.baiku.boot.core.orm.request.RequestFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import javax.persistence.criteria.CriteriaBuilder.In;
import java.util.*;

public class JpaSpecification<T> implements Specification<T> {
    private Collection<RequestFilter> filters = new ArrayList<>();
    private Logger log = LoggerFactory.getLogger(JpaSpecification.class);

    public JpaSpecification() {

    }

    public JpaSpecification(Collection<RequestFilter> filters) {
        this.filters = filters;
    }


    public JpaSpecification(RequestFilter filters) {
        this.filters.add(filters);
    }

    public void addFilter(RequestFilter requestFilter) {
        this.filters.add(requestFilter);
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
        try {
            if (filters != null && !filters.isEmpty()) {
                List<Predicate> predicates = new ArrayList<Predicate>();
                for (RequestFilter filter : filters) {
                    // nested path translate, 如Task的名为"user.name"的filedName, 转换为Task.user.name属性
                    String[] names = filter.fieldName.split("\\.");
                    Path expression = root.get(names[0]);
                    for (int i = 1; i < names.length; i++) {
                        expression = expression.get(names[i]);
                    }

                    switch (filter.operator) {
                        case EQ:
                            predicates.add(builder.equal(expression, filter.value));
                            break;
                        case NE:
                            predicates.add(builder.notEqual(expression, filter.value));
                            break;
                        case LLIKE:
                            predicates.add(builder.like(expression, "%" + filter.value));
                            break;
                        case RLIKE:
                            predicates.add(builder.like(expression, filter.value + "%"));
                            break;
                        case LIKE:
                            predicates.add(builder.like(expression, "%" + filter.value + "%"));
                            break;
                        case GT:
                            predicates.add(builder.greaterThan(expression, (Comparable) filter.value));
                            break;
                        case LT:
                            predicates.add(builder.lessThan(expression, (Comparable) filter.value));
                            break;
                        case GTE:
                            predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filter.value));
                            break;
                        case LTE:
                            predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filter.value));
                            break;
                        case ISNULL:
                            predicates.add(builder.isNull(expression));
                            break;
                        case ISNOTNULL:
                            predicates.add(builder.isNotNull(expression));
                            break;
                        case IN:
                            Collection value = (Collection) filter.value;
                            Iterator iterator = value.iterator();
                            if (value.size() == 1) {
                                //如果只有一个元素,则直接用EQ
                                predicates.add(builder.equal(expression, iterator.next()));
                                break;
                            }
                            In in = builder.in(expression);
                            while (iterator.hasNext()) {
                                in.value(iterator.next());
                            }
                            predicates.add(in);
                            break;
                        case NIN:
                            Collection notInValue = (Collection) filter.value;
                            Iterator notInIterator = notInValue.iterator();
                            if (notInValue.size() == 1) {
                                //如果只有一个元素,则直接用EQ
                                predicates.add(builder.notEqual(expression, notInIterator.next()));
                                break;
                            }
                            In notIn = builder.in(expression);
                            while (notInIterator.hasNext()) {
                                notIn.value(notInIterator.next());
                            }
                            predicates.add(builder.not(notIn));
                            break;
                        default:
                    }
                }
                // 将所有条件用 and 联合起来
                if (predicates.size() > 0) {
                    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
                }
            }
            return builder.conjunction();
        } finally {
            AjavaerConfig ajavaerConfig = SpringContext.getIfPresent(AjavaerConfig.class);
            if (ajavaerConfig != null && ajavaerConfig.getDebug() && ObjectTools.isNotBlank(this.filters)) {
                String showQuery = JsonTools.beanToJson(filterMap());
                log.debug("Query {},Params:{}", root.getJavaType().getSimpleName(), showQuery);
            }
        }
    }

    public Map<String, Object> filterMap() {
        Map<String, Object> result = new HashMap<>(filters.size());
        if (ObjectTools.isNotBlank(filters)) {
            for (RequestFilter filter : filters) {
                result.put(filter.operator + "_" + filter.fieldName, filter.value);
            }
        }
        return result;
    }
}
